﻿// This code is released under the BSD license.
namespace PugLib.UI
{
    using System;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime.InteropServices;

    /// <summary>
    /// The TaskDialog common button flags used to specify the builtin bottons to show in the TaskDialog.
    /// </summary>
    [Flags]
    internal enum TaskDialogCommonButtons
    {
        /// <summary>
        /// No common buttons.
        /// </summary>
        None = 0,

        /// <summary>
        /// OK common button. If selected Task Dialog will return DialogResult.OK.
        /// </summary>
        Ok = 0x0001,

        /// <summary>
        /// Yes common button. If selected Task Dialog will return DialogResult.Yes.
        /// </summary>
        Yes = 0x0002,

        /// <summary>
        /// No common button. If selected Task Dialog will return DialogResult.No.
        /// </summary>
        No = 0x0004,

        /// <summary>
        /// Cancel common button. If selected Task Dialog will return DialogResult.Cancel.
        /// If this button is specified, the dialog box will respond to typical cancel actions (Alt-F4 and Escape).
        /// </summary>
        Cancel = 0x0008,

        /// <summary>
        /// Retry common button. If selected Task Dialog will return DialogResult.Retry.
        /// </summary>
        Retry = 0x0010,

        /// <summary>
        /// Close common button. If selected Task Dialog will return this value.
        /// </summary>
        Close = 0x0020,
    }

    /// <summary>
    /// The System icons the TaskDialog supports.
    /// </summary>
    [SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32")] // Type comes from CommCtrl.h
    [CLSCompliant(false)]
    public enum TaskDialogIcon : uint
    {
        /// <summary>
        /// No Icon.
        /// </summary>
        None = 0,

        /// <summary>
        /// System warning icon.
        /// </summary>
        Warning = 0xFFFF, // MAKEINTRESOURCEW(-1)

        /// <summary>
        /// System Error icon.
        /// </summary>
        Error = 0xFFFE, // MAKEINTRESOURCEW(-2)

        /// <summary>
        /// System Information icon.
        /// </summary>
        Information = 0xFFFD, // MAKEINTRESOURCEW(-3)

        /// <summary>
        /// Shield icon.
        /// </summary>
        Shield = 0xFFFC, // MAKEINTRESOURCEW(-4)
    }

    /// <summary>
    /// TASKDIALOG_FLAGS taken from CommCtrl.h.
    /// </summary>
    [Flags]
    internal enum TASKDIALOG_FLAGS
    {
        /// <summary>
        /// Enable hyperlinks.
        /// </summary>
        TDF_ENABLE_HYPERLINKS = 0x0001,

        /// <summary>
        /// Use icon handle for main icon.
        /// </summary>
        TDF_USE_HICON_MAIN = 0x0002,

        /// <summary>
        /// Use icon handle for footer icon.
        /// </summary>
        TDF_USE_HICON_FOOTER = 0x0004,

        /// <summary>
        /// Allow dialog to be cancelled, even if there is no cancel button.
        /// </summary>
        TDF_ALLOW_DIALOG_CANCELLATION = 0x0008,

        /// <summary>
        /// Use command links rather than buttons.
        /// </summary>
        TDF_USE_COMMAND_LINKS = 0x0010,

        /// <summary>
        /// Use command links with no icons rather than buttons.
        /// </summary>
        TDF_USE_COMMAND_LINKS_NO_ICON = 0x0020,

        /// <summary>
        /// Show expanded info in the footer area.
        /// </summary>
        TDF_EXPAND_FOOTER_AREA = 0x0040,

        /// <summary>
        /// Expand by default.
        /// </summary>
        TDF_EXPANDED_BY_DEFAULT = 0x0080,

        /// <summary>
        /// Start with verification flag already checked.
        /// </summary>
        TDF_VERIFICATION_FLAG_CHECKED = 0x0100,

        /// <summary>
        /// Show a progress bar.
        /// </summary>
        TDF_SHOW_PROGRESS_BAR = 0x0200,

        /// <summary>
        /// Show a marquee progress bar.
        /// </summary>
        TDF_SHOW_MARQUEE_PROGRESS_BAR = 0x0400,

        /// <summary>
        /// Callback every 200 milliseconds.
        /// </summary>
        TDF_CALLBACK_TIMER = 0x0800,

        /// <summary>
        /// Center the dialog on the owner window rather than the monitor.
        /// </summary>
        TDF_POSITION_RELATIVE_TO_WINDOW = 0x1000,

        /// <summary>
        /// Right to Left Layout.
        /// </summary>
        TDF_RTL_LAYOUT = 0x2000,

        /// <summary>
        /// No default radio button.
        /// </summary>
        TDF_NO_DEFAULT_RADIO_BUTTON = 0x4000,

        /// <summary>
        /// Task Dialog can be minimized.
        /// </summary>
        TDF_CAN_BE_MINIMIZED = 0x8000
    }

    /// <summary>
    /// Task Dialog callback notifications. 
    /// </summary>
    public enum TaskDialogNotification
    {
        /// <summary>
        /// Sent by the Task Dialog once the dialog has been created and before it is displayed.
        /// The value returned by the callback is ignored.
        /// </summary>
        Created = 0,

        /// <summary>
        /// Sent by the Task Dialog when the user selects a button or command link in the task dialog.
        /// The button ID corresponding to the button selected will be available in the
        /// TaskDialogNotificationArgs. To prevent the Task Dialog from closing, the application must
        /// return true, otherwise the Task Dialog will be closed and the button ID returned to via
        /// the original application call.
        /// </summary>
        ButtonClicked = 2, // wParam = Button ID

        /// <summary>
        /// Sent by the Task Dialog when the user clicks on a hyperlink in the Task Dialog’s content.
        /// The string containing the HREF of the hyperlink will be available in the
        /// TaskDialogNotificationArgs. To prevent the TaskDialog from shell executing the hyperlink,
        /// the application must return TRUE, otherwise ShellExecute will be called.
        /// </summary>
        HyperlinkClicked = 3, // lParam = (LPCWSTR)pszHREF

        /// <summary>
        /// Sent by the Task Dialog approximately every 200 milliseconds when TaskDialog.CallbackTimer
        /// has been set to true. The number of milliseconds since the dialog was created or the
        /// notification returned true is available on the TaskDialogNotificationArgs. To reset
        /// the tickcount, the application must return true, otherwise the tickcount will continue to
        /// increment.
        /// </summary>
        Timer = 4, // wParam = Milliseconds since dialog created or timer reset

        /// <summary>
        /// Sent by the Task Dialog when it is destroyed and its window handle no longer valid.
        /// The value returned by the callback is ignored.
        /// </summary>
        Destroyed = 5,

        /// <summary>
        /// Sent by the Task Dialog when the user selects a radio button in the task dialog.
        /// The button ID corresponding to the button selected will be available in the
        /// TaskDialogNotificationArgs.
        /// The value returned by the callback is ignored.
        /// </summary>
        RadioButtonClicked = 6, // wParam = Radio Button ID

        /// <summary>
        /// Sent by the Task Dialog once the dialog has been constructed and before it is displayed.
        /// The value returned by the callback is ignored.
        /// </summary>
        DialogConstructed = 7,

        /// <summary>
        /// Sent by the Task Dialog when the user checks or unchecks the verification checkbox.
        /// The verificationFlagChecked value is available on the TaskDialogNotificationArgs.
        /// The value returned by the callback is ignored.
        /// </summary>
        VerificationClicked = 8, // wParam = 1 if checkbox checked, 0 if not, lParam is unused and always 0

        /// <summary>
        /// Sent by the Task Dialog when the user presses F1 on the keyboard while the dialog has focus.
        /// The value returned by the callback is ignored.
        /// </summary>
        Help = 9,

        /// <summary>
        /// Sent by the task dialog when the user clicks on the dialog's expando button.
        /// The expanded value is available on the TaskDialogNotificationArgs.
        /// The value returned by the callback is ignored.
        /// </summary>
        ExpandoButtonClicked = 10 // wParam = 0 (dialog is now collapsed), wParam != 0 (dialog is now expanded)
    }

    /// <summary>
    /// A custom button for the TaskDialog.
    /// </summary>
    [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] // Would be unused code as not required for usage.
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 1)]
    public struct TaskDialogButton
    {
        /// <summary>
        /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked.
        /// </summary>
        private int _buttonId;

        /// <summary>
        /// The string that appears on the button.
        /// </summary>
        [MarshalAs(UnmanagedType.LPWStr)]
        private string _buttonText;

        /// <summary>
        /// Initialize the custom button.
        /// </summary>
        /// <param name="id">The ID of the button. This value is returned by TaskDialog.Show when
        /// the button is clicked. Typically this will be a value in the DialogResult enum.</param>
        /// <param name="text">The string that appears on the button.</param>
        public TaskDialogButton(int id, string text)
        {
            _buttonId = id;
            _buttonText = text;
        }

        /// <summary>
        /// The ID of the button. This value is returned by TaskDialog.Show when the button is clicked.
        /// </summary>
        public int ButtonId
        {
            get { return _buttonId; }
            set { _buttonId = value; }
        }

        /// <summary>
        /// The string that appears on the button.
        /// </summary>
        public string ButtonText
        {
            get { return _buttonText; }
            set { _buttonText = value; }
        }
    }

    /// <summary>
    /// A Task Dialog. This is like a MessageBox but with many more features. TaskDialog requires Windows Longhorn or later.
    /// </summary>
    public class TaskDialog
    {
        /// <summary>
        /// Specifies the custom push _buttons to display in the dialog. Use CommonButtons member for
        /// common _buttons; OK, Yes, No, Retry and Cancel, and Buttons when you want different text
        /// on the push _buttons.
        /// </summary>
        private TaskDialogButton[] _buttons;

        /// <summary>
        /// The string to be used for the dialog’s primary _content. If the EnableHyperlinks member is true,
        /// then this string may contain hyperlinks in the form: <A HREF="executablestring">Hyperlink Text</A>. 
        /// WARNING: Enabling hyperlinks when using _content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        private string _content;

        /// <summary>
        /// Indicates the default button for the dialog. This may be any of the values specified
        /// in ButtonId members of one of the TaskDialogButton structures in the Buttons array,
        /// or one a DialogResult value that corresponds to a _buttons specified in the CommonButtons Member.
        /// If this member is zero or its value does not correspond to any button ID in the dialog,
        /// then the first button in the dialog will be the default. 
        /// </summary>
        private int _defaultButton;


        /// <summary>
        /// The _flags passed to TaskDialogIndirect.
        /// </summary>
        private TASKDIALOG_FLAGS _flags;

        /// <summary>
        /// Specifies a built in icon for the main icon in the dialog. If this is set to none
        /// and the CustomMainIcon is null then no main icon will be displayed.
        /// </summary>
        private TaskDialogIcon _mainIcon;

        /// <summary>
        /// The string to be used for the main instruction.
        /// </summary>
        private string _mainInstruction;

        /// <summary>
        /// The string to be used for the dialog box title. If this parameter is NULL, the filename of the executable program is used.
        /// </summary>
        private string _windowTitle;

        /// <summary>
        /// Creates a default Task Dialog.
        /// </summary>
        public TaskDialog(string windowTitle, string mainText, string content)
        {
            Reset();

            WindowTitle = windowTitle;
            MainInstruction = mainText;
            Content = content;
            UseCommandLinks = true;
        }

        /// <summary>
        /// The string to be used for the dialog box title. If this parameter is NULL, the filename of the executable program is used.
        /// </summary>
        internal string WindowTitle
        {
            get { return _windowTitle; }
            set { _windowTitle = value; }
        }

        /// <summary>
        /// The string to be used for the main instruction.
        /// </summary>
        internal string MainInstruction
        {
            get { return _mainInstruction; }
            set { _mainInstruction = value; }
        }

        /// <summary>
        /// The string to be used for the dialog’s primary _content. If the EnableHyperlinks member is true,
        /// then this string may contain hyperlinks in the form: <A HREF="executablestring">Hyperlink Text</A>. 
        /// WARNING: Enabling hyperlinks when using _content from an unsafe source may cause security vulnerabilities.
        /// </summary>
        internal string Content
        {
            get { return _content; }
            set { _content = value; }
        }

        /// <summary>
        /// Specifies the push _buttons displayed in the dialog box. This parameter may be a combination of _flags.
        /// If no common _buttons are specified and no custom _buttons are specified using the Buttons member, the
        /// dialog box will contain the OK button by default.
        /// </summary>
        internal TaskDialogCommonButtons CommonButtons { get; private set; }

        /// <summary>
        /// Specifies a built in icon for the main icon in the dialog. If this is set to none
        /// and the CustomMainIcon is null then no main icon will be displayed.
        /// </summary>
        [CLSCompliant(false)]
        public TaskDialogIcon MainIcon
        {
            get { return _mainIcon; }
            set { _mainIcon = value; }
        }

        /// <summary>
        /// Specifies the custom push _buttons to display in the dialog. Use CommonButtons member for
        /// common _buttons; OK, Yes, No, Retry and Cancel, and Buttons when you want different text
        /// on the push _buttons.
        /// </summary>
        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")] // Style of use is like single value. Array is of value types.
        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays")] // Returns a reference, not a copy.
        public TaskDialogButton[] Buttons
        {
            get { return _buttons; }

            set
            {
                if (value == null)
                {
                    throw new ArgumentNullException("value");
                }

                _buttons = value;
            }
        }

        /// <summary>
        /// Indicates that the _buttons specified in the Buttons member should be displayed as command links
        /// (using a standard task dialog glyph) instead of push _buttons.  When using command links, all
        /// characters up to the first new line character in the ButtonText member (of the TaskDialogButton
        /// structure) will be treated as the command link’s main text, and the remainder will be treated
        /// as the command link’s note. This flag is ignored if the Buttons member has no entires.
        /// </summary>
        internal bool UseCommandLinks
        {
            get { return (_flags & TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS) != 0; }
            set { SetFlag(TASKDIALOG_FLAGS.TDF_USE_COMMAND_LINKS, value); }
        }

        /// <summary>
        /// Indicates that the TaskDialog should be positioned (centered) relative to the owner window
        /// passed when calling Show. If not set (or no owner window is passed), the TaskDialog is
        /// positioned (centered) relative to the monitor.
        /// </summary>
        public bool PositionRelativeToWindow
        {
            get { return (_flags & TASKDIALOG_FLAGS.TDF_POSITION_RELATIVE_TO_WINDOW) != 0; }
            set { SetFlag(TASKDIALOG_FLAGS.TDF_POSITION_RELATIVE_TO_WINDOW, value); }
        }

        /// <summary>
        /// Indicates that the TaskDialog should have right to left layout.
        /// </summary>
        public bool RightToLeftLayout
        {
            get { return (_flags & TASKDIALOG_FLAGS.TDF_RTL_LAYOUT) != 0; }
            set { SetFlag(TASKDIALOG_FLAGS.TDF_RTL_LAYOUT, value); }
        }

        /// <summary>
        /// Indicates the default button for the dialog. This may be any of the values specified
        /// in ButtonId members of one of the TaskDialogButton structures in the Buttons array,
        /// or one a DialogResult value that corresponds to a _buttons specified in the CommonButtons Member.
        /// If this member is zero or its value does not correspond to any button ID in the dialog,
        /// then the first button in the dialog will be the default. 
        /// </summary>
        public int DefaultButton
        {
            get { return _defaultButton; }
            set { _defaultButton = value; }
        }

        /// <summary>
        /// Resets the Task Dialog to the state when first constructed, all properties set to their default value.
        /// </summary>
        internal void Reset()
        {
            _windowTitle = null;
            _mainInstruction = null;
            _content = null;
            CommonButtons = 0;
            _mainIcon = TaskDialogIcon.None;
            _buttons = new TaskDialogButton[0];
            _flags = 0;
            _defaultButton = 0;
        }

        /// <summary>
        /// Creates, displays, and operates a task dialog. The task dialog contains application-defined messages, title,
        /// verification check box, command links and push _buttons, plus any combination of predefined icons and push _buttons
        /// as specified on the other members of the class before calling Show.
        /// </summary>
        /// 
        /// <returns>The result of the dialog, either a DialogResult value for common push _buttons set in the CommonButtons
        /// member or the ButtonID from a TaskDialogButton structure set on the Buttons member.</returns>
        public int Show()
        {
            // Hand it off to emulator.
            EmulateTaskDialog taskDialogEmulate = new EmulateTaskDialog(this);
            taskDialogEmulate.ShowDialog();
            int result = taskDialogEmulate.TaskDialogResult;
            return result;
        }


        /// <summary>
        /// Helper function to set or clear a bit in the _flags field.
        /// </summary>
        /// <param name="flag">The Flag bit to set or clear.</param>
        /// <param name="value">True to set, false to clear the bit in the _flags field.</param>
        private void SetFlag(TASKDIALOG_FLAGS flag, bool value)
        {
            if (value)
            {
                _flags |= flag;
            }
            else
            {
                _flags &= ~flag;
            }
        }
    }
}